package com.gitee.loyo.act

import kotlinx.coroutines.runBlocking
import com.gitee.loyo.*
import com.gitee.loyo.act.remote.RemoteTransferorFactory
import com.gitee.loyo.interfaces.LazyReadMapper
import com.gitee.loyo.interfaces.TypeReference
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Proxy
import kotlin.reflect.KClass

interface IModelCompanion<M : Model<M, R>, R : Record<M, R>> {
    fun find(fn: (M.() -> Unit)? = null): List<R>
    fun findResult(fn: (M.() -> Unit)? = null): LazyReadMapper
    fun find(param: R): List<R>
    fun findPage(fn: (M.() -> Unit)? = null): Pair<Int, List<R>>
    fun findPage(param: R, offset: Int, limit: Int): Pair<Int, List<R>>
    fun findOne(fn: (M.() -> Unit)? = null): R?
    fun findOne(param: R): R?
    fun findByID(value: Any): R?
    fun findByIDs(fn: M.() -> Array<Pair<Column<out Any>, Any>>): R?
    fun findForUpdate(fn: (M.() -> Unit)? = null): List<R>
    fun findInShareMode(fn: (M.() -> Unit)? = null): List<R>
    fun count(fn: (M.() -> Unit)? = null): Int
    fun count(param: R): Int
    fun firstOrNull(fn: (M.() -> Unit)? = null): R?
    fun lastOrNull(fn: (M.() -> Unit)? = null): R?
    fun take(n: Int, fn: (M.() -> Unit)? = null): List<R>
    fun last(n: Int, fn: (M.() -> Unit)? = null): List<R>
    fun add(recode: R): Boolean
    fun create(recode: R): Any?
    fun update(record: R, fn: (M.() -> Unit)? = null): Boolean
    fun update(fn: M.() -> Unit): Boolean
    fun remove(fn: (M.() -> Unit)? = null): Int

    companion object{

        fun <T: IModelCompanion<M, R>, M : Model<M, R>, R : Record<M, R>> proxy(operator: ModelOperator<M, R>, createModel: () -> M): T{
            val delegate = ModelCompanionDelegate(operator, createModel)
            return Proxy.newProxyInstance(
                KSQL::class.java.classLoader,
                arrayOf(IModelCompanion::class.java),
            ){ _, method, arg ->
                method.invoke(delegate, *arg)
            } as T
        }
    }
}

class ModelCompanionDelegate<M : Model<M, R>, R : Record<M, R>>(
    private val operator: ModelOperator<M, R>,
    private val createModel: () -> M
) : IModelCompanion<M, R>{

    override fun find(fn: (M.() -> Unit)?): List<R>{
        return runBlocking {
            operator.find(createModel(), fn)
        }
    }

    override fun findResult(fn: (M.() -> Unit)?): LazyReadMapper {
        return runBlocking {
            operator.findResult(createModel(), fn)
        }
    }

    override fun find(param: R): List<R>{
        return runBlocking {
            operator.find(createModel(), param)
        }
    }

    override fun findPage(fn: (M.() -> Unit)?): Pair<Int, List<R>> {
        return runBlocking {
            val count = operator.count(createModel(), fn)
            if(count == 0){
                count to emptyList()
            }else{
                count to operator.find(createModel(), fn)
            }
        }
    }

    override fun findPage(param: R, offset: Int, limit: Int): Pair<Int, List<R>> {
        return runBlocking {
            val count = operator.count(createModel(), param)
            if(count == 0){
                count to emptyList()
            }else{
                count to operator.find(createModel().also { it.limit(offset, limit) }, param)
            }
        }
    }

    override fun findOne(fn: (M.() -> Unit)?): R?{
        return runBlocking {
            operator.findOne(createModel(), fn)
        }
    }

    override fun findOne(param: R): R?{
        return runBlocking {
            operator.findOne(createModel(), param)
        }
    }

    override fun findByID(value: Any): R?{
        return runBlocking {
            operator.findByID(createModel(), value)
        }
    }

    override fun findByIDs(fn: M.() -> Array<Pair<out Column<out Any>, out Any>>): R?{
        return runBlocking {
            operator.findByIDs(createModel(), fn)
        }
    }

    override fun findForUpdate(fn: (M.() -> Unit)?): List<R>{
        return runBlocking {
            operator.findForUpdate(createModel(), fn)
        }
    }

    override fun findInShareMode(fn: (M.() -> Unit)?): List<R>{
        return runBlocking {
            operator.findInShareMode(createModel(), fn)
        }
    }

    override fun count(fn: (M.() -> Unit)?): Int{
        return runBlocking {
            operator.count(createModel(), fn)
        }
    }

    override fun count(param: R): Int{
        return runBlocking {
            operator.count(createModel(), param)
        }
    }

    override fun firstOrNull(fn: (M.() -> Unit)?): R?{
        return runBlocking {
            operator.firstOrNull(createModel(), fn)
        }
    }

    override fun lastOrNull(fn: (M.() -> Unit)?): R?{
        return runBlocking {
            operator.lastOrNull(createModel(), fn)
        }
    }

    override fun take(n: Int, fn: (M.() -> Unit)?): List<R>{
        return runBlocking {
            operator.take(createModel(), n, fn)
        }
    }

    override fun last(n: Int, fn: (M.() -> Unit)?): List<R>{
        return runBlocking {
            operator.last(createModel(), n, fn)
        }
    }

    override fun add(record: R): Boolean{
        return runBlocking {
            operator.add(createModel(), record)
        }
    }

    override fun create(record: R): Any? {
        return runBlocking {
            operator.create(createModel(), record)
        }
    }

    override fun update(record: R, fn: (M.() -> Unit)?): Boolean {
        return runBlocking {
            operator.update(createModel(), record)
        }
    }

    override fun update(fn: M.() -> Unit): Boolean {
        return runBlocking {
            operator.update(createModel(), fn)
        }
    }

    override fun remove(fn: (M.() -> Unit)?): Int{
        return runBlocking {
            operator.remove(createModel(), fn)
        }
    }
}

open class ModelCompanion<M: Model<M, R>, R: Record<M, R>>(
    ref: ModelCompanionRef<M, R>,
    protected val operator: ModelOperator<M, R> = ModelOperator()
): IModelCompanion<M, R>{
    private val modelKClass: KClass<M> by lazy {
        ((javaClass.superclass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<M>).kotlin
    }
    private val recordKClass: KClass<R> by lazy {
        ((javaClass.superclass.genericSuperclass as ParameterizedType).actualTypeArguments[1] as Class<R>).kotlin
    }

    init {
        ref.ref = this
    }

    inline fun <reified T: Any> findOthers(noinline fn: (M.() -> Unit)? = null): List<T>{
        return findResult(fn).asList(object : TypeReference<T>(){})
    }

    inline fun <reified T: Any> findOther(noinline fn: (M.() -> Unit)? = null): T?{
        return findResult(fn).`as`(T::class)
    }

    operator fun get(pattern: String): IModelCompanion<M, R>{
        return IModelCompanion.proxy(operator) { createModel(pattern) }
    }

    fun createModel(pattern: String = ""): M {
        return Helper.createModel(modelKClass, pattern)
    }

    override fun find(fn: (M.() -> Unit)?): List<R> {
        return IModelCompanion.proxy(operator) { createModel() }.find(fn)
    }

    override fun findResult(fn: (M.() -> Unit)?): LazyReadMapper {
        return IModelCompanion.proxy(operator) { createModel() }.findResult(fn)
    }

    override fun find(param: R): List<R> {
        return IModelCompanion.proxy(operator) { createModel() }.find(param)
    }

    override fun findPage(fn: (M.() -> Unit)?): Pair<Int, List<R>> {
        return IModelCompanion.proxy(operator) { createModel() }.findPage(fn)
    }

    override fun findPage(param: R, offset: Int, limit: Int): Pair<Int, List<R>> {
        return IModelCompanion.proxy(operator) { createModel() }.findPage(param, offset, limit)
    }

    override fun findOne(fn: (M.() -> Unit)?): R? {
        return IModelCompanion.proxy(operator) { createModel() }.findOne(fn)
    }

    override fun findOne(param: R): R? {
        return IModelCompanion.proxy(operator) { createModel() }.findOne(param)
    }

    override fun findByID(value: Any): R? {
        return IModelCompanion.proxy(operator) { createModel() }.findByID(value)
    }

    override fun findByIDs(fn: M.() -> Array<Pair<Column<out Any>, Any>>): R? {
        return IModelCompanion.proxy(operator) { createModel() }.findByIDs(fn)
    }

    override fun findForUpdate(fn: (M.() -> Unit)?): List<R> {
        return IModelCompanion.proxy(operator) { createModel() }.findForUpdate(fn)
    }

    override fun findInShareMode(fn: (M.() -> Unit)?): List<R> {
        return IModelCompanion.proxy(operator) { createModel() }.findInShareMode(fn)
    }

    override fun count(fn: (M.() -> Unit)?): Int {
        return IModelCompanion.proxy(operator) { createModel() }.count(fn)
    }

    override fun count(param: R): Int {
        return IModelCompanion.proxy(operator) { createModel() }.count(param)
    }

    override fun firstOrNull(fn: (M.() -> Unit)?): R? {
        return IModelCompanion.proxy(operator) { createModel() }.firstOrNull(fn)
    }

    override fun lastOrNull(fn: (M.() -> Unit)?): R? {
        return IModelCompanion.proxy(operator) { createModel() }.lastOrNull(fn)
    }

    override fun take(n: Int, fn: (M.() -> Unit)?): List<R> {
        return IModelCompanion.proxy(operator) { createModel() }.take(n, fn)
    }

    override fun last(n: Int, fn: (M.() -> Unit)?): List<R> {
        return IModelCompanion.proxy(operator) { createModel() }.last(n, fn)
    }

    override fun add(record: R): Boolean {
        return IModelCompanion.proxy(operator) { createModel() }.add(record)
    }

    fun add(records: List<R>) {
        return Helper.insertModelBatch(records, createModel().getTableNameWithAlias())
    }

    override fun create(record: R): Any? {
        return IModelCompanion.proxy(operator) { createModel() }.create(record)
    }

    override fun remove(fn: (M.() -> Unit)?): Int {
        return IModelCompanion.proxy(operator) { createModel() }.remove(fn)
    }

    override fun update(record: R, fn: (M.() -> Unit)?): Boolean {
        return IModelCompanion.proxy(operator) { createModel() }.update(record, fn)
    }

    override fun update(fn: M.() -> Unit): Boolean {
        return IModelCompanion.proxy(operator) { createModel() }.update(fn)
    }

    fun getTransferorFactory(): TransferorFactory {
        return operator.transferorFactory
    }
}

open class RemoteModelCompanion<M: Model<M, R>, R: Record<M, R>>(ref: ModelCompanionRef<M, R>): ModelCompanion<M, R>(ref, ModelOperator(RemoteTransferorFactory))

class ModelCompanionRef<M: Model<M, R>, R: Record<M, R>>{
    lateinit var ref: ModelCompanion<M, R>

    inline fun <reified T> dao(): T{
        return Proxy.newProxyInstance(
            KSQL::class.java.classLoader,
            arrayOf<Class<*>>(T::class.java)
        ){ _, method, args ->
            val argsMap = Helper.buildArgsMap(null, method, args?.toList())
            ref.getTransferorFactory().invoke(ref::class.java, method, argsMap)
        } as T
    }
}